/**
 * 
 */
package com.otrace.service;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import com.otrace.vo.ResultVO;
import com.otrace.vo.VirtualMachineVO;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.MethodExitEvent;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import com.sun.jdi.request.MethodExitRequest;

/**
 * @author Prabhu KVN
 * 
 */
public class VirtualMachineService {

	private VirtualMachineVO vmVO;
	public List<ResultVO> resultList;

	/**
	 * fetch virtual machine related details. and put it in a VM VO.
	 * 
	 * @param virtualMachine
	 * @return
	 */
	public VirtualMachineVO fetchJVMDetails(VirtualMachine virtualMachine) {
		if (virtualMachine != null) {


			vmVO = new VirtualMachineVO();

		}
		return vmVO;
	}

	/**
	 * Add class filter criteria for virtual machine. add method exit and method
	 * entry events.
	 * 
	 * @param vm
	 * @param includeClasses
	 */
	public List<ResultVO> enableEventCapture(VirtualMachine vm,
			Vector<String> includeClasses) {

		resultList = new ArrayList<ResultVO>();
		// / get Event Manager and enable method exit and method entry events.
		EventRequestManager eventRequestManager = vm.eventRequestManager();
		eventRequestManager.deleteEventRequests(eventRequestManager.methodEntryRequests());
		eventRequestManager.deleteEventRequests(eventRequestManager.methodExitRequests());
		for (Enumeration<String> patterns = includeClasses.elements(); patterns
				.hasMoreElements();) {
			MethodEntryRequest methodEntryRequest = eventRequestManager
					.createMethodEntryRequest();
			String pattern = patterns.nextElement();
			methodEntryRequest.addClassFilter(pattern);
			methodEntryRequest.enable();
			MethodExitRequest methodExitRequest = eventRequestManager
					.createMethodExitRequest();
			methodExitRequest.addClassFilter(pattern);
			methodExitRequest.enable();
		}

		EventQueue eventQueue = vm.eventQueue();
		boolean running = true;
		int callCount = 1;
		while (running) {
			EventSet eventSet = null;
			Map<String, String> variablesMap = new HashMap<String, String>();
			Set<Method> methods = new HashSet<Method>();

			try {
				if(callCount==1){
				eventSet = eventQueue.remove();
				}else{
					eventSet = eventQueue.remove(5000);
				}
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if(eventSet!=null){
				ResultVO resultVO = new ResultVO();
				String callFlow ="";
				resultVO.setCallCount(callCount);
			for (Event event : eventSet) {
				if (event instanceof MethodEntryEvent) {
					callFlow = processMethodEntryEvent((MethodEntryEvent) event,
							variablesMap, methods);
				}
				if (event instanceof MethodExitEvent) {
					callFlow = processMethodExitEvent((MethodExitEvent) event,
							variablesMap, methods);
				}
			}
			/*System.out.println("Call " + callCount + ":"
					+ "-----------------------------------");*/
			resultVO.setCallFlow(callFlow);
			resultVO.setVariablesMap(variablesMap);
			resultVO.setMethods(methods);
			resultList.add(resultVO);
		/*	for (String key : variablesMap.keySet()) {
				System.out.println(key + "=" + variablesMap.get(key));
			}
			for(Method m1: methods){
				System.out.println(m1);
			}
		*/	
			callCount++;
			vm.resume();
			}else{
				break;
			}

		}
		return resultList;

	}

	private String processMethodExitEvent(MethodExitEvent event,
			Map<String, String> variablesMap, Set<Method> methods) {

		Method method = event.method();
		String methodNameString = method.name();
		ThreadReference thread = event.thread();
		String callFlow ="";
		try {
			List<StackFrame> stackFrames = thread.frames();
			int level = 0;
			long calleId = 0;
			String calleClass = null;
			long callerId = 0;
			String callerClass = null;
			//System.out.println("Total Frames : " + stackFrames.size());

			for (StackFrame stackFrame : stackFrames) {
				ObjectReference objectReference = stackFrame.thisObject();
				

				if (level == 0) {
					calleId = objectReference.uniqueID();
					calleClass = objectReference.referenceType().toString();
					printDetails(objectReference, stackFrame, variablesMap,methods);
				}
				if (level == 1) {
					callerId = objectReference.uniqueID();
					callerClass = objectReference.referenceType().toString();
					// printDetails(objectReference, stackFrame,variablesMap);
				}

				level++;
				if (level > 1) {
					break;
				}
			}
			/*
			 * System.out.println("" + callerId + ":" + callerClass + "" +
			 * methodNameString + "" + calleId + ":" + calleClass + "" +
			 * thread.name() + "");
			 */
			callFlow =  "" +":" + callerClass.substring(0,callerClass.indexOf('('))
					+ " Exits --> " + methodNameString + " --> On " + ":" + calleClass.substring(0,calleClass.indexOf('('));

		} catch (IncompatibleThreadStateException e) {
			e.printStackTrace();
			//virtualMachine.dispose();
		}
	
return callFlow;
	}

	private String processMethodEntryEvent(MethodEntryEvent event,
			Map<String, String> variablesMap, Set<Method> methods) {

		Method method = event.method();
		String methodNameString = method.name();
		ThreadReference thread = event.thread();
		String callFlow ="";
		try {
			List<StackFrame> stackFrames = thread.frames();
			int level = 0;
			long calleId = 0;
			String calleClass = null;
			long callerId = 0;
			String callerClass = null;
			//System.out.println("Total Frames : " + stackFrames.size());

			for (StackFrame stackFrame : stackFrames) {
				ObjectReference objectReference = stackFrame.thisObject();

				if (level == 0) {
					calleId = objectReference.uniqueID();
					calleClass = objectReference.referenceType().toString();
					printDetails(objectReference, stackFrame, variablesMap,methods);
				}
				if (level == 1) {
					callerId = objectReference.uniqueID();
					callerClass = objectReference.referenceType().toString();
					// printDetails(objectReference, stackFrame,variablesMap);
				}

				level++;
				if (level > 1) {
					break;
				}
			}
			/*
			 * System.out.println("" + callerId + ":" + callerClass + "" +
			 * methodNameString + "" + calleId + ":" + calleClass + "" +
			 * thread.name() + "");
			 */
			callFlow = "" + ":" + callerClass.substring(0,callerClass.indexOf('('))
					+ " Called --> " + methodNameString + " --> On "+ ":" + calleClass.substring(0,calleClass.indexOf('('));

		} catch (IncompatibleThreadStateException e) {
			e.printStackTrace();
			//virtualMachine.dispose();
		}
	return callFlow;

	}
	public void printDetails(ObjectReference objectReference,
			StackFrame stackFrame, Map<String, String> variablesMap, Set<Method> methodList) {

		try {

			ReferenceType referenceType = objectReference.referenceType();
			List<Field> fields = referenceType.fields();
			for (Field field : fields) {

				Value value = objectReference.getValue(field);

				if (value instanceof ArrayReference) {
					if (value != null) {
						// System.out.println(((ArrayReference)value).getValues());
						StringBuffer sb = new StringBuffer();
						for (Value v2 : ((ArrayReference) value).getValues()) {
							sb.append(v2 + ",");
						}

						variablesMap.put(field.name(), sb.toString());

					}
				} else if (value instanceof ObjectReference) {
					if (value != null) {
						ObjectReference object = (ObjectReference) value;
						ReferenceType rType = object.referenceType();
						boolean isAList = false;
						List<Field> f = rType.fields();
						for (Field f1 : f) {
							Value v = object.getValue(f1);
							if (v instanceof ArrayReference
									&& f1.name().contains("elementData")) {
								/*
								 * System.out.println(field + ":" + f1.name() +
								 * ":" + ((ArrayReference) v).getValues());
								 */
								StringBuffer sb = new StringBuffer();
								for (Value v3 : ((ArrayReference) v)
										.getValues()) {
									sb.append(v3 + ",");
								}
								variablesMap.put(field.name()+":"+f1.name(), sb.toString());
								isAList = true;
							}
						}
						// if Object reference is not the type of List
						if (!isAList) {
							variablesMap.put(field.name(), ""
									+ objectReference.getValue(field));
						}

					}
				} else {
					variablesMap.put(field.name(), ""
							+ objectReference.getValue(field));

				}

				/*
				 * System.out.println(field.name() + " : " +
				 * objectReference.getValue(field));
				 */
			}
			// all methods
			List<Method> methods = referenceType.methods();
			for (Method m : methods) {
				//System.out.println("Method : " + m);
			
				methodList.add(m);

				List<LocalVariable> args = m.arguments();
				List<LocalVariable> frameLocals = stackFrame.visibleVariables();
				for (LocalVariable var : args) {

					//System.out.println("Method Args : " + var.name());
					if (frameLocals.contains(var)) {
						if (var.isVisible(stackFrame)) {
							variablesMap.put(var.name(), ""
									+ stackFrame.getValue(var));

						}
					}
				}
				//System.out.println("args : " + args);
				// method level variables
				List<LocalVariable> localVars = m.variables();
				//System.out.println("ALL STACK VALUES:"+stackFrame.getArgumentValues());

				for (LocalVariable lVar : localVars) {
					if (frameLocals.contains(lVar)) {
						
						/* System.out .println(" Method local vaiable : " +
						 lVar.name());*/
						 if (lVar.isVisible(stackFrame)) {
							variablesMap.put(lVar.name(), ""
									+ stackFrame.getValue(lVar));
							
						/*	  System.out.println(" : value ** -- " +
							  stackFrame.getValue(lVar));
						*/	 
						}
					}
				}
			//System.out.println("Method Locals : " + localVars);
			}
		} catch (AbsentInformationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//virtualMachine.dispose();
		}
	}

}